﻿#region

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Configuration.Provider;
using System.Data.OleDb;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Web.Security;
using Norm;

#endregion

namespace Jazz.MongoDbProviders
{
    public class MongoDbMembershipProvider : MembershipProvider
    {
        private const int SaltSizeInBytes = 16;
        private const int PasswordSize = 14;
        protected readonly IConnectionStringStore ConnectionStringStore;
        protected readonly IValidationKeyInfo ValidationKeyInfo;
        private string _appName;
        private int _applicationId;
        private DateTime _applicationIdCacheDate;
        private string _connectionString;
        private bool _enablePasswordReset;
        private bool _enablePasswordRetrieval;
        private string _hashAlgorithmType;
        private int _maxInvalidPasswordAttempts;
        private int _minRequiredNonalphanumericCharacters;
        private int _minRequiredPasswordLength;
        private int _passwordAttemptWindow;
        private MembershipPasswordFormat _passwordFormat;
        private Regex _passwordStrengthRegex;
        private string _passwordStrengthRegularExpression;
        private bool _requiresQuestionAndAnswer;
        private bool _requiresUniqueEmail;

        public MongoDbMembershipProvider()
            : this(new ConfigurationManagerConnectionStringStore(), new ValidationKeyInfo("system.web/machineKey"))
        {
        }

        public MongoDbMembershipProvider(IConnectionStringStore connectionStringStore,
                                         IValidationKeyInfo validationKeyInfo)
        {
            ConnectionStringStore = connectionStringStore;
            ValidationKeyInfo = validationKeyInfo;
        }

        #region Public properties

        public override bool EnablePasswordRetrieval
        {
            get { return _enablePasswordRetrieval; }
        }

        public override bool EnablePasswordReset
        {
            get { return _enablePasswordReset; }
        }

        public override bool RequiresQuestionAndAnswer
        {
            get { return _requiresQuestionAndAnswer; }
        }

        public override bool RequiresUniqueEmail
        {
            get { return _requiresUniqueEmail; }
        }

        public override MembershipPasswordFormat PasswordFormat
        {
            get { return _passwordFormat; }
        }

        public override int MaxInvalidPasswordAttempts
        {
            get { return _maxInvalidPasswordAttempts; }
        }

        public override int PasswordAttemptWindow
        {
            get { return _passwordAttemptWindow; }
        }

        public override int MinRequiredPasswordLength
        {
            get { return _minRequiredPasswordLength; }
        }

        public override int MinRequiredNonAlphanumericCharacters
        {
            get { return _minRequiredNonalphanumericCharacters; }
        }

        public override string PasswordStrengthRegularExpression
        {
            get { return _passwordStrengthRegularExpression; }
        }

        public override string ApplicationName
        {
            get { return _appName; }
            set
            {
                if (_appName != value)
                {
                    _applicationId = 0;
                    _appName = value;
                }
            }
        }

        #endregion

        public override void Initialize(string name, NameValueCollection config)
        {
            if (config == null)
            {
                throw new ArgumentNullException("config");
            }
            if (string.IsNullOrEmpty(name))
            {
                name = GetType().Name;
            }
            if (string.IsNullOrEmpty(config["description"]))
            {
                config.Remove("description");
                config.Add("description", string.Format("Membership {0} Provider", name));
            }
            base.Initialize(name, config);

            _connectionString = ConnectionStringStore.GetConnectionString(config["connectionStringName"]);
            if (string.IsNullOrEmpty(_connectionString))
            {
                throw new ProviderException("Connection string cannot be blank.");
            }

            _enablePasswordRetrieval = MongoDbUtility.GetBooleanValue(config, "enablePasswordRetrieval", false);
            _enablePasswordReset = MongoDbUtility.GetBooleanValue(config, "enablePasswordReset", true);
            _requiresQuestionAndAnswer = MongoDbUtility.GetBooleanValue(config, "requiresQuestionAndAnswer", true);
            _requiresUniqueEmail = MongoDbUtility.GetBooleanValue(config, "requiresUniqueEmail", true);
            _maxInvalidPasswordAttempts = MongoDbUtility.GetIntValue(config, "maxInvalidPasswordAttempts", 5, false, 0);
            _passwordAttemptWindow = MongoDbUtility.GetIntValue(config, "passwordAttemptWindow", 10, false, 0);
            _minRequiredPasswordLength = MongoDbUtility.GetIntValue(config, "minRequiredPasswordLength", 7, false, 128);
            _minRequiredNonalphanumericCharacters = MongoDbUtility.GetIntValue(config,
                                                                               "minRequiredNonalphanumericCharacters", 1,
                                                                               true, 128);

            _hashAlgorithmType = config["hashAlgorithmType"];
            if (string.IsNullOrEmpty(_hashAlgorithmType))
            {
                _hashAlgorithmType = "SHA1";
            }

            _passwordStrengthRegularExpression = config["passwordStrengthRegularExpression"];
            if (_passwordStrengthRegularExpression != null)
            {
                _passwordStrengthRegularExpression = _passwordStrengthRegularExpression.Trim();
                if (_passwordStrengthRegularExpression.Length != 0)
                {
                    try
                    {
                        _passwordStrengthRegex = new Regex(_passwordStrengthRegularExpression);
                    }
                    catch (ArgumentException e)
                    {
                        throw new ProviderException(e.Message, e);
                    }
                }
            }
            else
            {
                _passwordStrengthRegularExpression = string.Empty;
                _passwordStrengthRegex = new Regex(@".*");
            }

            _appName = config["ApplicationName"];
            if (string.IsNullOrEmpty(_appName))
            {
                _appName = MongoDbUtility.GetDefaultAppName();
            }

            if (_appName.Length > 255)
            {
                throw new ProviderException("Provider application name is too long, max length is 255.");
            }

            string strTemp = config["passwordFormat"] ?? "Hashed";

            switch (strTemp)
            {
                case "Clear":
                    _passwordFormat = MembershipPasswordFormat.Clear;
                    break;
                case "Encrypted":
                    _passwordFormat = MembershipPasswordFormat.Encrypted;
                    break;
                case "Hashed":
                    _passwordFormat = MembershipPasswordFormat.Hashed;
                    break;
                default:
                    throw new ProviderException("Bad password format");
            }

            if (_passwordFormat == MembershipPasswordFormat.Hashed && _enablePasswordRetrieval)
            {
                throw new ProviderException("Provider cannot retrieve hashed password");
            }


            config.Remove("connectionStringName");
            config.Remove("enablePasswordRetrieval");
            config.Remove("enablePasswordReset");
            config.Remove("requiresQuestionAndAnswer");
            config.Remove("ApplicationName");
            config.Remove("requiresUniqueEmail");
            config.Remove("maxInvalidPasswordAttempts");
            config.Remove("passwordAttemptWindow");
            config.Remove("passwordFormat");
            config.Remove("name");
            config.Remove("description");
            config.Remove("minRequiredPasswordLength");
            config.Remove("minRequiredNonalphanumericCharacters");
            config.Remove("passwordStrengthRegularExpression");
            config.Remove("hashAlgorithmType");
            if (config.Count > 0)
            {
                string attribUnrecognized = config.GetKey(0);
                if (!string.IsNullOrEmpty(attribUnrecognized))
                    throw new ProviderException("Provider unrecognized attribute: " + attribUnrecognized);
            }
        }

        private string GenerateSalt()
        {
            var buf = new byte[SaltSizeInBytes];
            (new RNGCryptoServiceProvider()).GetBytes(buf);
            return Convert.ToBase64String(buf);
        }

        private string EncodePassword(string pass, int passwordFormat, string salt)
        {
            if (passwordFormat == 0) // MembershipPasswordFormat.Clear
                return pass;

            byte[] bIn = Encoding.Unicode.GetBytes(pass);
            byte[] bSalt = Convert.FromBase64String(salt);
            byte[] bAll = new byte[bSalt.Length + bIn.Length];
            byte[] bRet;

            Buffer.BlockCopy(bSalt, 0, bAll, 0, bSalt.Length);
            Buffer.BlockCopy(bIn, 0, bAll, bSalt.Length, bIn.Length);
            if (passwordFormat == 1)
            {
                // MembershipPasswordFormat.Hashed
                HashAlgorithm s = HashAlgorithm.Create(_hashAlgorithmType);

                // If the hash algorithm is null (and came from config), throw a config exception
                if (s == null)
                {
                    throw new ProviderException("Could not create a hash algorithm");
                }
                bRet = s.ComputeHash(bAll);
            }
            else
            {
                bRet = EncryptPassword(bAll);
            }

            return Convert.ToBase64String(bRet);
        }

        private string UnEncodePassword(string pass, int passwordFormat)
        {
            switch (passwordFormat)
            {
                case 0: // MembershipPasswordFormat.Clear:
                    return pass;
                case 1: // MembershipPasswordFormat.Hashed:
                    throw new ProviderException("Provider can not decode hashed password");
                default:
                    byte[] bIn = Convert.FromBase64String(pass);
                    byte[] bRet = DecryptPassword(bIn);
                    if (bRet == null)
                        return null;
                    return Encoding.Unicode.GetString(bRet, SaltSizeInBytes, bRet.Length - SaltSizeInBytes);
            }
        }

        private bool CheckPassword(OleDbConnection connection, int userId, string password, out int status)
        {
            string salt;
            bool userIsApproved;
            int passwordFormat;
            string pass = GetPasswordWithFormat(connection, userId, null, false, out passwordFormat, out status,
                                                out salt, out userIsApproved);
            string pass2 = EncodePassword(password, passwordFormat, salt);
            return (pass == pass2);
        }


        private bool CheckPassword(OleDbConnection connection, int userId, string password, out bool userIsApproved)
        {
            string salt;
            int passwordFormat, status;
            string pass = GetPasswordWithFormat(connection, userId, null, false, out passwordFormat, out status,
                                                out salt, out userIsApproved);
            string pass2 = EncodePassword(password, passwordFormat, salt);
            return (pass == pass2);
        }


        private string GetPasswordWithFormat(
            OleDbConnection connection,
            int userId,
            string passwordAnswer,
            bool requiresQuestionAndAnswer,
            out int passwordFormat,
            out int status,
            out string passwordSalt,
            out bool userIsApproved)
        {
            OleDbCommand command;
            OleDbDataReader reader;
            string storedPasswordAnswer;
            string pass;

            passwordFormat = 0;
            status = 1; // status = user not found
            passwordSalt = String.Empty;
            userIsApproved = false;
            if (userId == 0)
                return null;

            command = new OleDbCommand(@"SELECT PasswordFormat, [Password], PasswordAnswer, PasswordSalt, IsApproved " +
                                       @"FROM aspnet_Membership m, aspnet_Users u " +
                                       @"WHERE m.UserId = @UserId AND m.UserId = u.UserId",
                                       connection);
            command.Parameters.Add(new OleDbParameter("@UserId", userId));

            reader = command.ExecuteReader();

            if (!reader.Read())
            {
                // Zero rows read = user-not-found
                reader.Close();
                return null;
            }

            passwordFormat = reader.GetInt32(0);
            pass = GetNullableString(reader, 1);
            storedPasswordAnswer = GetNullableString(reader, 2);
            passwordSalt = GetNullableString(reader, 3);
            userIsApproved = reader.GetBoolean(4);

            if (requiresQuestionAndAnswer &&
                String.Compare(storedPasswordAnswer, passwordAnswer, true, CultureInfo.InvariantCulture) != 0)
            {
                status = 3;
                pass = null;
            }
            else
            {
                status = 0;
            }
            reader.Close();
            return pass;
        }

        private int GetAppplicationId(MongoDbConnectionHolder holder)
        {
            if (_applicationId != 0 && holder.CreateDate < _applicationIdCacheDate) // Already cached?
                return _applicationId;
            string appName = _appName;
            if (appName.Length > 255)
                appName = appName.Substring(0, 255);
            _applicationId = MongoDbConnectionHelper.GetApplicationID(holder.Connection, appName, true);
            _applicationIdCacheDate = DateTime.UtcNow;
            if (_applicationId != 0)
                return _applicationId;
            throw new ProviderException(GetExceptionText(20));
        }

        private string GetNullableString(OleDbDataReader reader, int col)
        {
            if (reader.IsDBNull(col) == false)
            {
                return reader.GetString(col);
            }

            return null;
        }

        private string GetExceptionText(int status)
        {
            string key;
            switch (status)
            {
                case 0:
                    return String.Empty;
                case 1:
                    key = "User not found";
                    break;
                case 2:
                    key = "Wrong password";
                    break;
                case 3:
                    key = "Wrong answer";
                    break;
                case 4:
                    key = "Invalid password";
                    break;
                case 5:
                    key = "Invalid question";
                    break;
                case 6:
                    key = "Invalid answer";
                    break;
                case 7:
                    key = "Invalid email";
                    break;
                default:
                    key = "Unknown provider error";
                    break;
            }
            return key;
        }

        private bool IsStatusDueToBadPassword(int status)
        {
            return (status >= 2 && status <= 6);
        }

        public virtual string GeneratePassword()
        {
            return Membership.GeneratePassword(
                MinRequiredPasswordLength < PasswordSize ? PasswordSize : MinRequiredPasswordLength,
                MinRequiredNonAlphanumericCharacters);
        }

        private MembershipUser UserToMembershipUser(User user)
        {
            return new MembershipUser(Name,
                                      user.UserName,
                                      user.Id,
                                      user.Email,
                                      user.PasswordQuestion,
                                      user.Comment,
                                      user.IsApproved,
                                      user.IsLockedOut,
                                      user.CreationDate,
                                      user.LastLoginDate,
                                      user.LastActivityDate,
                                      user.LastPasswordChangedDate,
                                      user.LastLockedOutDate);
        }

        #region New

        public override bool ChangePassword(string userName, string oldPassword, string newPassword)
        {
            if (!ValidateUser(userName, oldPassword))
                return false;

            var args = new ValidatePasswordEventArgs(userName, newPassword, false);

            OnValidatingPassword(args);

            if (args.Cancel)
            {
                if (args.FailureInformation != null)
                {
                    throw args.FailureInformation;
                }
                throw new MembershipPasswordException(
                    "Change password cancelled due to new password validation failure.");
            }

            User user = UpdateUser(userName, found => found.Password = EncodePassword(newPassword));

            return (user != null);
        }

        public override bool ChangePasswordQuestionAndAnswer(string userName,
                                                             string password,
                                                             string newPwdQuestion,
                                                             string newPwdAnswer)
        {
            if (!ValidateUser(userName, password))
                return false;

            UpdateUser(userName, delegate(User updating)
                                     {
                                         updating.PasswordQuestion = newPwdQuestion;
                                         updating.PasswordAnswer = newPwdAnswer;
                                     });

            return true;
        }

        public override MembershipUser CreateUser(string userName,
                                                  string password,
                                                  string email,
                                                  string passwordQuestion,
                                                  string passwordAnswer,
                                                  bool isApproved,
                                                  object providerUserKey,
                                                  out MembershipCreateStatus status)
        {
            var args = new ValidatePasswordEventArgs(userName, password, true);

            OnValidatingPassword(args);

            if (args.Cancel)
            {
                status = MembershipCreateStatus.InvalidPassword;
                return null;
            }

            if (RequiresUniqueEmail && GetUserNameByEmail(email) != "")
            {
                status = MembershipCreateStatus.DuplicateEmail;
                return null;
            }

            MembershipUser dbUser = GetUser(userName, false);

            if (dbUser == null)
            {
                DateTime createDate = DateTime.UtcNow;

                if (providerUserKey == null)
                {
                    providerUserKey = Guid.NewGuid();
                }
                else
                {
                    if (!(providerUserKey is Guid))
                    {
                        status = MembershipCreateStatus.InvalidProviderUserKey;
                        return null;
                    }
                }

                var user = new User
                               {
                                   UserName = userName,
                                   Password = EncodePassword(password),
                                   Email = email,
                                   PasswordQuestion = passwordQuestion,
                                   PasswordAnswer = EncodePassword(passwordAnswer),
                                   IsApproved = isApproved,
                                   Comment = "",
                                   CreationDate = createDate,
                                   LastPasswordChangedDate = createDate,
                                   LastActivityDate = createDate,
                                   ApplicationName = ApplicationName,
                                   IsLockedOut = false,
                                   Id = (Guid) providerUserKey,
                                   /*
                    LastLockedOutDate = createDate,
            FailedPasswordAttemptCount = 0,
                    FailedPasswordAttemptWindowStart = createDate,
            FailedPasswordAnswerAttemptCount = 0,
                    FailedPasswordAnswerAttemptWindowStart = createDate*/
                               };

                (new MongoSession(_connectionString)).Save(user);
                status = MembershipCreateStatus.Success;

                return GetUser(userName, false);
            }

            status = MembershipCreateStatus.DuplicateUserName;
            return null;
        }

        public override bool DeleteUser(string userName, bool deleteAllRelatedData)
        {
            using (var mongo = Mongo.Create(_connectionString))
            {
                var userCollection = mongo.GetCollection<User>();
                User user = userCollection.AsQueryable().Where(
                    u => u.UserName == userName && u.ApplicationName == ApplicationName).FirstOrDefault();
                if (user == null)
                {
                    return false;
                }

                userCollection.Delete(user);
            }

            return true;
        }

        public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
        {
            return FindUsers(
                u => u.ApplicationName == ApplicationName,
                pageIndex,
                pageSize,
                out totalRecords);
        }

        public override int GetNumberOfUsersOnline()
        {
            TimeSpan onlineSpan = new TimeSpan(0, Membership.UserIsOnlineTimeWindow, 0);
            DateTime compareTime = DateTime.UtcNow.Subtract(onlineSpan);

            using (var dbc = Mongo.Create(_connectionString))
            {
                return
                    dbc.GetCollection<User>().AsQueryable().Where(
                        u => u.LastActivityDate > compareTime && u.ApplicationName == ApplicationName).Count();
            }
        }

        public override string GetPassword(string userName, string answer)
        {
            if (!EnablePasswordRetrieval)
                throw new ProviderException("Password Retrieval not enabled.");

            if (PasswordFormat == MembershipPasswordFormat.Hashed)
                throw new ProviderException("Cannot retrieve Hashed passwords.");

            User found = GetUser(u => u.UserName == userName && u.ApplicationName == ApplicationName, false);

            if (found == null)
                throw new MembershipPasswordException("The supplied user name is not found.");

            if (found.IsLockedOut)
                throw new MembershipPasswordException("The supplied user is locked out.");

            if (RequiresQuestionAndAnswer && !CheckPassword(answer, found.PasswordAnswer))
            {
                UpdateUser(userName,
                           updating => updating.UpdateFailureCount(UserAttemptFailureType.PasswordAnswer, this));

                throw new MembershipPasswordException("Incorrect password answer.");
            }

            if (PasswordFormat == MembershipPasswordFormat.Encrypted)
                return UnEncodePassword(found.Password);

            return found.Password;
        }

        public override MembershipUser GetUser(string userName, bool userIsOnline)
        {
            User found = GetUser(user => user.UserName == userName && user.ApplicationName == ApplicationName, userIsOnline);
            if (found != null)
            {
                return UserToMembershipUser(found);
            }
            return null;
        }

        public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
        {
            User found = GetUser(
                user => user.Id == (Guid) providerUserKey && user.ApplicationName == ApplicationName, userIsOnline);
            if (found != null)
            {
                return UserToMembershipUser(found);
            }
            return null;
        }

        private User GetUser(Expression<Func<User, bool>> userPredicate, bool userIsOnline)
        {
            using (var dbc = Mongo.Create(_connectionString))
            {
                var users = dbc.GetCollection<User>();
                var found = users.AsQueryable().Where(userPredicate).FirstOrDefault();

                if (userIsOnline)
                {
                    found.LastActivityDate = DateTime.UtcNow;
                    users.Save(found);
                }

                return found;
            }
        }

        private IEnumerable<User> GetUsers(Expression<Func<User, bool>> userPredicate)
        {
            using (var dbc = Mongo.Create(_connectionString))
            {
                return dbc.GetCollection<User>().AsQueryable().Where(userPredicate).ToList();
            }
        }

        public override bool UnlockUser(string userName)
        {
            User found = UpdateUser(userName,
                                    updating =>
                                        {
                                            updating.IsLockedOut = false;
                                            updating.LastLockedOutDate = DateTime.UtcNow;
                                        });

            return found != null;
        }

        public override string GetUserNameByEmail(string email)
        {
            User found = GetUser(user => (user.Email == email && user.ApplicationName == ApplicationName), false);

            return found == null ? "" : found.UserName;
        }

        public override string ResetPassword(string username, string answer)
        {
            if (!EnablePasswordReset)
                throw new NotSupportedException("Password reset is not enabled.");

            User user = GetUser(u => u.UserName == username && u.ApplicationName == ApplicationName, false);
            if (user == null)
                throw new ProviderException("The supplied user name is not found.");

            if (String.IsNullOrEmpty(answer) && RequiresQuestionAndAnswer)
            {
                user.UpdateFailureCount(UserAttemptFailureType.PasswordAnswer, this);
                throw new ProviderException("Password answer required for password reset.");
            }

            string newPassword = GenerateTempPassword(this.MinRequiredPasswordLength,
                                                      MinRequiredNonAlphanumericCharacters);

            var args = new ValidatePasswordEventArgs(username, newPassword, false);

            OnValidatingPassword(args);

            if (args.Cancel)
            {
                if (args.FailureInformation != null)
                    throw args.FailureInformation;

                throw new MembershipPasswordException("Reset password cancelled due to password validation failure.");
            }

            if (user.IsLockedOut)
                throw new MembershipPasswordException("The supplied user is locked out.");

            if (RequiresQuestionAndAnswer && !CheckPassword(answer, user.PasswordAnswer))
            {
                user.UpdateFailureCount(UserAttemptFailureType.PasswordAnswer, this);
                throw new MembershipPasswordException("Incorrect password answer.");
            }

            UpdateUser(user.UserName,
                       updating =>
                           {
                               updating.Password = EncodePassword(newPassword);
                               updating.LastPasswordChangedDate = DateTime.UtcNow;
                           });

            return newPassword;
        }

        /// <summary>
        /// Membership.GeneratePassword can generates some unnecessary complex password,
        /// whereas this produces passwords much more pleasant especially if no alphanumerics are required.
        /// </summary>
        protected static string GenerateTempPassword(int length, int minNonAlphanumeric)
        {
            int modVal = 3;

            if (minNonAlphanumeric == 0)
                modVal = 2;
            else if (minNonAlphanumeric*3 > length)
                length = minNonAlphanumeric*3;

            byte[] buffer = new byte[length];
            new Random((int) DateTime.UtcNow.Ticks).NextBytes(buffer);

            string password = "";

            for (int i = 0; i < length; i++)
            {
                if (i%modVal == 0)
                    password = password + (char) ('a' + (buffer[i]%26));
                else if (i%modVal == 1)
                    password = password + (char) ('0' + (buffer[i]%10));
                else
                    password = password + (char) ('#' + (buffer[i]%4));
            }

            return password;
        }

        public override void UpdateUser(MembershipUser user)
        {
            UpdateUser(u => u.UserName == user.UserName && u.ApplicationName == ApplicationName,
                       updating =>
                           {
                               updating.Email = user.Email;
                               updating.Comment = user.Comment;
                               updating.IsApproved = user.IsApproved;
                           });
        }

        public User UpdateUser(string userName, Action<User> userUpdate)
        {
            return UpdateUser(u => u.UserName == userName && u.ApplicationName == ApplicationName, userUpdate);
        }

        private User UpdateUser(Expression<Func<User, bool>> userPredicate, Action<User> userUpdate)
        {
            var user = GetUser(userPredicate, false);

            if (user != null)
            {
                user.LastActivityDate = DateTime.UtcNow;
                if (userUpdate != null)
                {
                    userUpdate(user);
                    new MongoSession(_connectionString).Save(user);
                }
            }
            return user;
        }

        public override bool ValidateUser(string userName, string password)
        {
            bool isValid = false;

            User user = UpdateUser(userName,
                                   updating =>
                                       {
                                           if (CheckPassword(password, updating.Password))
                                           {
                                               if (updating.IsApproved)
                                               {
                                                   isValid = true;
                                                   updating.LastLoginDate = DateTime.UtcNow;
                                               }
                                           }
                                           else
                                           {
                                               updating.UpdateFailureCount(UserAttemptFailureType.Password, this);
                                           }
                                       }
                );

            if (user == null)
            {
                return false;
            }

            return isValid;
        }

        private bool CheckPassword(string password, string dbPassword)
        {
            string pass1 = password;
            string pass2 = dbPassword;

            switch (PasswordFormat)
            {
                case MembershipPasswordFormat.Encrypted:
                    pass2 = UnEncodePassword(dbPassword);
                    break;
                case MembershipPasswordFormat.Hashed:
                    pass1 = EncodePassword(password);
                    break;
                default:
                    break;
            }

            if (pass1 == pass2)
                return true;

            return false;
        }

        private string EncodePassword(string password)
        {
            string encodedPassword = password;

            switch (PasswordFormat)
            {
                case MembershipPasswordFormat.Clear:
                    break;
                case MembershipPasswordFormat.Encrypted:
                    encodedPassword = Convert.ToBase64String(EncryptPassword(Encoding.Unicode.GetBytes(password)));
                    break;
                case MembershipPasswordFormat.Hashed:
                    HMACSHA1 hash = new HMACSHA1 {Key = HexToByte(ValidationKeyInfo.GetKey())};
                    encodedPassword = Convert.ToBase64String(hash.ComputeHash(Encoding.Unicode.GetBytes(password)));
                    break;
                default:
                    throw new ProviderException("Unsupported password format.");
            }

            return encodedPassword;
        }

        private string UnEncodePassword(string encodedPassword)
        {
            string password = encodedPassword;

            switch (PasswordFormat)
            {
                case MembershipPasswordFormat.Clear:
                    break;
                case MembershipPasswordFormat.Encrypted:
                    password =
                        Encoding.Unicode.GetString(DecryptPassword(Convert.FromBase64String(password)));
                    break;
                case MembershipPasswordFormat.Hashed:
                    throw new ProviderException("Cannot unencode a hashed password.");
                default:
                    throw new ProviderException("Unsupported password format.");
            }

            return password;
        }

        private static byte[] HexToByte(string hexString)
        {
            byte[] returnBytes = new byte[hexString.Length/2];
            for (int i = 0; i < returnBytes.Length; i++)
                returnBytes[i] = Convert.ToByte(hexString.Substring(i*2, 2), 16);
            return returnBytes;
        }

        public override MembershipUserCollection FindUsersByName(string userNameToMatch, int pageIndex, int pageSize,
                                                                 out int totalRecords)
        {
            return FindUsers(
                u => u.UserName.Contains(userNameToMatch) && u.ApplicationName == ApplicationName,
                pageIndex,
                pageSize,
                out totalRecords);
        }

        public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize,
                                                                  out int totalRecords)
        {
            return FindUsers(
                u => u.Email.Contains(emailToMatch) && u.ApplicationName == ApplicationName,
                pageIndex,
                pageSize,
                out totalRecords);
        }

        private MembershipUserCollection FindUsers(Expression<Func<User, bool>> predicate, int pageIndex, int pageSize,
                                                   out int totalRecords)
        {
            List<User> list = GetUsers(predicate).ToList();
            totalRecords = list.Count;

            list.Sort(
                (left, right) => left.UserName.CompareTo(right.UserName));

            MembershipUserCollection result = new MembershipUserCollection();

            int start = pageIndex*pageSize;
            int end = (start + pageSize < totalRecords) ? start + pageSize : totalRecords;

            if (start < end)
            {
                for (int i = start; i < end; i++)
                    result.Add(UserToMembershipUser(list[i]));
            }

            return result;
        }

        #endregion
    }
}